package work

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
	"github.com/robfig/cron/v3"
	"seat-service/constant"
	"seat-service/initialization"
	"strconv"
	"strings"
	"sync"
	"time"
)

type Handler interface {
	Handle(channel, message string)
}

type Queue struct {
	R       *redis.Client //redis服务
	Channel string        //订阅频道
	Handler               //接口
}

func GetQueue(r *redis.Client, c string, h Handler) *Queue {
	q := &Queue{
		R:       r,
		Channel: c,
		Handler: h,
	}
	return q
}

func (q *Queue) Read() {
	publish := q.R.Subscribe(context.Background(), q.Channel)
	defer publish.Close()
	ch := publish.Channel()
	for msg := range ch {
		//订阅频道的处理逻辑
		q.Handle(msg.Channel, msg.Payload)
	}
}

type DelayQueue struct {
	Key          string //zset中member
	Channel      string //频道
	T            int64  //秒
	mu           sync.Locker
	reservations map[string]Reservation //[预约单id]Reservation
}
type Reservation struct {
	Content     string //publish发送的内容
	Time        int64  //正式开始的时间戳
	Cancelled   bool   // 取消标志 false正常提醒，true不提醒
	CancelMutex sync.Locker
}

func Use() {
	appointmentReminder()
	default1()
	go CheckSeatStatus()
}
func CheckSeatStatus() {
	c := cron.New()
	addFunc, err := c.AddFunc("* * * * *", func() {
		//判断当前预约表中的预约是否到结束时间，如果到结束时间，
		//将预约状态修改为0，即取消预约
		//但此时会有一个漏洞，对于某个座位，假设用户在cron运行后的1秒时结束使用，在cron下次执行前，别的用户申请了（因为预约时判断能否预约，是根据reserve表中的时间来决定）
		//此时将不会改变这个座位的状态，状态还是为已使用，这是不合理的
		//目前的方案是，在这个基础上添加一个状态的判断，如果当前状态为1，即已预约未使用状态，则不做任何操作
		initialization.DB.Table("seats").Where("seat_reserve = 2").
			Where("id IN (SELECT seat_id FROM reserves WHERE appointment_time_end < ?)", time.Now()).
			Update("seat_reserve", 0)
	})
	if err != nil {
		initialization.SeatLogger.Error("定时任务：每分钟执行检查和修改座位状态出错，错误：" + err.Error() + ",addFunc:" + strconv.Itoa(int(addFunc)))
	}
	c.Start()
}

//func (d *DelayQueue) Delay() {
//	for {
//		z, err := initialization.Redis.ZPopMin(context.Background(), d.Key).Result()
//		if err != nil {
//			fmt.Println(err.Error())
//		} else if len(z) == 0 {
//			time.Sleep(500)
//		} else {
//			go func() {
//				score := int64(z[0].Score)
//				member := z[0].Member.(string)
//				if score-time.Now().Unix() > d.T {
//					fmt.Println(score, time.Now().Unix())
//					a := score - time.Now().Unix() - d.T + 1
//					fmt.Println(member, "进入休眠，休眠时间：", a)
//					time.Sleep(time.Second * time.Duration(a))
//				}
//				initialization.Redis.Publish(context.Background(), d.Channel, z[0].Member)
//			}()
//		}
//	}
//}

// 签到提醒结构体
var (
	appointmentMap map[string]Reservation
	default1Map    map[string]Reservation
)

//// 违约，主要用于外部发送停止信号
//func default1StopPush(reserveID string) {
//	default1Map[reserveID].CancelMutex.Lock()
//	default1Map[reserveID] = Reservation{
//		Cancelled: true,
//	}
//	default1Map[reserveID].CancelMutex.Unlock()
//
//}
//
//// 签到提醒，主要用于外部发送停止信号
//func AppointmentStopPush(reserveID string) {
//	appointmentMap[reserveID].CancelMutex.Lock()
//	appointmentMap[reserveID] = Reservation{
//		Cancelled: true,
//	}
//	appointmentMap[reserveID].CancelMutex.Unlock()
//}

// 学生取消预约时需要调用一下这个函数 reserveID 预约单id
func StopPush(reserveID string) {
	appointmentMap[reserveID].CancelMutex.Lock()
	appointmentMap[reserveID] = Reservation{
		Cancelled: true,
	}
	appointmentMap[reserveID].CancelMutex.Unlock()

	default1Map[reserveID].CancelMutex.Lock()
	default1Map[reserveID] = Reservation{
		Cancelled: true,
	}
	default1Map[reserveID].CancelMutex.Unlock()
}

// 签到提醒
func appointmentReminder() {

	q := GetQueue(initialization.Redis, constant.AppointmentChannel, &ApplicationHandler{})
	go q.Read()
	p := DelayQueue{
		Key:          constant.Appointment,
		Channel:      constant.AppointmentChannel,
		T:            600,
		mu:           &sync.Mutex{},
		reservations: make(map[string]Reservation),
	}
	appointmentMap = p.reservations
	//添加预约提醒信息到map中 key为预约单id
	go p.GetContent()
	go p.Delay()

	//q := GetQueue(initialization.Redis, constant.AppointmentChannel, &ApplicationHandler{})
	//go q.Read()
	//p := DelayQueue{
	//	Key:     constant.Appointment,
	//	Channel: constant.AppointmentChannel,
	//	T:       600,
	//	mu:      &sync.Mutex{},        // 初始化为 sync.Mutex
	//	Workers: make(map[string]int), //两个状态位0 1 ，在正常情况下状态位是0，如果外部想要不推送信息，就删除
	//}
	//go p.Delay()
}

// 违约
func default1() {
	q := GetQueue(initialization.Redis, constant.DefaultChannel, &DefaultHandler{})
	go q.Read()
	d := DelayQueue{
		Key:          constant.Sign,
		Channel:      constant.DefaultChannel,
		T:            600,
		mu:           &sync.Mutex{},
		reservations: make(map[string]Reservation),
	}
	//添加预约提醒信息到map中 key为预约单id
	go d.GetContent()
	go d.Delay()
}
func (d *DelayQueue) GetContent() {
	for {
		z, err := initialization.Redis.ZPopMin(context.Background(), d.Key).Result()
		if err != nil {
			fmt.Println(err.Error())
		} else if len(z) == 0 {
			time.Sleep(500)
		} else {
			d.mu.Lock()
			arr := strings.Split(z[0].Member.(string), ",")
			d.reservations[arr[1]] = Reservation{
				Content:     arr[0],
				Time:        int64(z[0].Score),
				Cancelled:   false,
				CancelMutex: &sync.Mutex{},
			}
			d.mu.Unlock()
		}
	}
}

func (d *DelayQueue) Delay() {
	ticker := time.NewTicker(time.Minute)
	defer ticker.Stop()

	//每分钟遍历一次map，查找已经预约的学生
	for range ticker.C {
		d.mu.Lock()

		for t, data := range d.reservations {
			bol := data.Cancelled
			if bol {
				//查到取消的预约单，删除键值对
				delete(d.reservations, t)
			} else {
				//查到未取消的预约单,判断时间
				//如果时间差大于等于0，小于等于600，则pub到redis中等待推送
				diff := data.Time - time.Now().Unix()
				if diff >= 0 && diff <= d.T {
					initialization.Redis.Publish(context.Background(), d.Channel, data.Content)
				}
			}
		}

		d.mu.Unlock()
	}
}
